package com.lastproject.ejb.dao;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.servlet.ServletContext;
import javax.xml.bind.DatatypeConverter;

import com.lastproject.dto.UserDTO;
import com.lastproject.ejb.entities.Role;
import com.lastproject.ejb.entities.User;
import com.lastproject.ejb.service.MailSender;
import com.lastproject.ejb.service.PasswordEncryption;
import com.lastproject.ejb.util.UserConversion;

/**
 * Session Bean implementation class UserDAO Author: Alexandra
 */
@Stateless
public class UserDAO implements UserDAORemote {

	@PersistenceContext
	EntityManager entityManager;

	@EJB
	UserConversion conv;

	public UserDAO() {

	}

	// @Interceptors(PermissionInterceptor.class)
	// @PermissionAnnotation(canCRUDOnEmployees=1)
	@Override
	public void create(UserDTO userDTO) {

		// initial password, represented as a string
		String tempPass = userDTO.getPassword();
		String password_encrypted2 = "";

		System.out.println("pass from gui: " + tempPass);

		try {

			// initial password after being hashed using SHA-1
			// represented as byte[]
			byte[] hashedPassword = new PasswordEncryption()
					.getEncryptedPassword(tempPass,
							new PasswordEncryption().getSalt());

			// hashed password stored as string
			// this version of the password will be stored in the db
			password_encrypted2 = DatatypeConverter
					.printHexBinary(hashedPassword);
			userDTO.setPassword(password_encrypted2);
		} catch (NoSuchAlgorithmException | InvalidKeySpecException
				| UnsupportedEncodingException e) {
			e.printStackTrace();
		}

		entityManager.persist(conv.fromDTO(userDTO));

		// send email with instructions to activate account
		sendInstructionsEmail("newAccount", userDTO.getWorkEmail(),
				userDTO.getUsername(), password_encrypted2);

	}

	@Override
	public UserDTO findById(int id) {
		User user = entityManager.find(User.class, id);
		return conv.fromEntity(user);
	}
	
	@Override
	public List<UserDTO> findByMultipleId(List<Integer> ids) {
		List<UserDTO> returnUsers = new ArrayList<UserDTO>();
		if(ids.isEmpty()){
			return returnUsers;
		}
		
		StringBuilder querySB = new StringBuilder("Select u From User u WHERE u.id IN (");
		for(int id : ids){
			querySB.append(id + ", ");
		}
		querySB.replace(querySB.lastIndexOf(", "), querySB.length(), ")");
		TypedQuery<User> query = entityManager.createQuery(querySB.toString(), User.class);
		List<User> users = query.getResultList();
		
		for(User u:users){
			returnUsers.add(conv.fromEntity(u));
		}
		return returnUsers;
	}

	@Override
	public List<UserDTO> getAll() {
		List<User> userList = entityManager.createQuery("Select u from User u Order By u.lastname, u.firstname",
				User.class).getResultList();

		List<UserDTO> usersDTO = new ArrayList<UserDTO>();

		for (User user : userList) {
			UserDTO uDTO = new UserDTO();
			uDTO = conv.fromEntity(user);
			usersDTO.add(uDTO);

		}

		return usersDTO;
	}

	@Override
	public void edit(UserDTO userDTO) {
		entityManager.merge(conv.fromDTO(userDTO));
	}

	@Override
	public void remove(UserDTO userDTO) {
		User user = entityManager.find(User.class, conv.fromDTO(userDTO)
				.getId());
		entityManager.remove(user);
	}

	public boolean existentUsername(String username) {
		List<User> userList = entityManager
				.createQuery(
						"Select u from User u "
								+ "where u.username = :username", User.class)
				.setParameter("username", username).getResultList();

		if (userList.size() != 0) {
			return true;
		} else
			return false;
	}

	public User findUserByCredentials(String username, String hashedPassword) {
		// find user by the credentials stored in the db
		List<User> userList = entityManager
				.createQuery(
						"Select u from User u "
								+ "where u.username = :username "
								+ "and u.password = :password "
								+ "and u.enabled = true", User.class)
				.setParameter("username", username)
				.setParameter("password", hashedPassword).getResultList();

		if (userList.size() != 0) {
			return userList.get(0);
		} else
			return null;
	}

	@Override
	public UserDTO findCreatorUserBySurvey(int idSurvey) {
		// find the creator of a survey
		List<User> userList = entityManager
				.createQuery(
						"Select u from User u JOIN u.surveys s WHERE s.id = :id",
						User.class).setParameter("id", idSurvey)
				.getResultList();

		UserDTO userDTO = conv.fromEntity(userList.get(0));
		return userDTO;

	}

	@Override
	public UserDTO userLogin(String logUsername, String logPassword) {
		// login user
		// can be accessed by anyone
		User logUser = null;
		UserDTO logUserDTO = null;
		byte[] hashedPasswordByte;
		String hashedPasswordString = "";

		PasswordEncryption passEncript = new PasswordEncryption();

		try {
			// hash password provided by user in GUI
			hashedPasswordByte = passEncript.getEncryptedPassword(logPassword,
					passEncript.getSalt());
			// convert hashed password in String
			hashedPasswordString = DatatypeConverter
					.printHexBinary(hashedPasswordByte);
		} catch (NoSuchAlgorithmException | InvalidKeySpecException
				| UnsupportedEncodingException e) {
			e.printStackTrace();
		} finally {
			// do nothing
		}

		if (findUserByCredentials(logUsername, hashedPasswordString) != null) {
			logUser = findUserByCredentials(logUsername, hashedPasswordString);
			logUserDTO = conv.fromEntity(logUser);
		}

		return logUserDTO;
	}

	@Override
	public UserDTO changePassword(String logUsername, String passToChange,
			String newPassword) {
		// can be accessed by anyone
		List<User> userList = entityManager
				.createQuery(
						"SELECT u FROM User u "
								+ "WHERE u.username = :logUsername", User.class)
				.setParameter("logUsername", logUsername).getResultList();

		String hashedNewPassword = "";

		if ((userList.size() != 0)
				&& (userList.get(0).getStatusPassword().equals("newPassword"))) {
			UserDTO userDTO = conv.fromEntity(userList.get(0));

			try {

				// new password after being hashed using SHA-1
				// represented as byte[]
				byte[] hashedPassword = new PasswordEncryption()
						.getEncryptedPassword(newPassword,
								new PasswordEncryption().getSalt());

				// hashed password stored as string
				// this version of the password will be stored in the db
				hashedNewPassword = DatatypeConverter
						.printHexBinary(hashedPassword);
				userDTO.setPassword(hashedNewPassword);

				// change password status from "newPassword"
				// to "changedPassword"
				userDTO.setStatusPassword("changePassword");
				userDTO.setEnabled(true);

				// update user info in db
				edit(userDTO);
			} catch (NoSuchAlgorithmException | InvalidKeySpecException
					| UnsupportedEncodingException e) {
				e.printStackTrace();
			}

			return userDTO;
		} else
			return null;
	}

	@Override
	public boolean requestChangePassword(String providedEmail) {
		// can be accessed by anyone
		List<User> userList = entityManager
				.createQuery(
						"SELECT u FROM User u "
								+ "WHERE u.workEmail = :providedEmail",
						User.class)
				.setParameter("providedEmail", providedEmail).getResultList();

		int randNr = 1 + (int) (Math.random() * 5);
		String generatedTempPassword = "" + randNr;
		String password_encrypted2 = "";

		if (userList.size() != 0) {
			UserDTO userDTO = conv.fromEntity(userList.get(0));

			userDTO.setStatusPassword("newPassword");

			try {

				// initial password after being hashed using SHA-1
				// represented as byte[]
				byte[] hashedPassword = new PasswordEncryption()
						.getEncryptedPassword(generatedTempPassword,
								new PasswordEncryption().getSalt());

				// hashed password stored as string
				// this version of the password will be stored in the db
				password_encrypted2 = DatatypeConverter
						.printHexBinary(hashedPassword);
				userDTO.setPassword(password_encrypted2);
			} catch (NoSuchAlgorithmException | InvalidKeySpecException
					| UnsupportedEncodingException e) {
				e.printStackTrace();
			}

			edit(userDTO);

			// send email with instructions to change password
			sendInstructionsEmail("changePw", userDTO.getWorkEmail(),
					userDTO.getUsername(), password_encrypted2);

			return true;
		} else
			return false;
	}

	@Override
	public UserDTO getLibrarian() {
		List<User> userList = entityManager
				.createQuery(
						"SELECT u FROM User u " + "JOIN u.roles r "
								+ "WHERE r.name = :roleName", User.class)
				.setParameter("roleName", "libraryAdmin").getResultList();

		if (userList.size() != 0) {
			UserDTO librarian = conv.fromEntity(userList.get(0));
			return librarian;
		} else
			return null;
	}

	public void sendInstructionsEmail(String emailPurpose,
			String receiverEmail, String receiverUsername, String encryptPass) {
		MailSender ms = new MailSender();

		String emailSender = "no.reply.internalapp@gmail.com";
		String emailSubject = "";
		String emailContent = "";

		// property file that contains ip address of server
		// on which project is run
		// at the moment - localhost
		Properties ipConfigFile = new Properties();
		// property file is stored in META-INF folder
		InputStream myIS = getClass().getClassLoader().getResourceAsStream(
				"META-INF/ipconfigs.properties");

		try {
			// open property file
			ipConfigFile.load(myIS);
		} catch (IOException e) {
			e.printStackTrace();
		}
		ServletContext servletContext = (ServletContext) FacesContext
			    .getCurrentInstance().getExternalContext().getContext();
		// get value of "ip" property
		String ipAddress = ipConfigFile.getProperty("ip");
		String firstAccessLink = ipAddress
				+ servletContext.getServletContextName() + "/faces/changePassword.xhtml" + "?logUsername="
				+ receiverUsername + "&logPassword=" + encryptPass;

		switch (emailPurpose) {

		case "newAccount":
			emailSubject = "Your account was created";
			emailContent = "Click the link provided below and follow the instructions"
					+ " to activate your account.\n\n" + firstAccessLink;
			break;
		case "changePw":
			emailSubject = "Password change request";
			emailContent = "We received a request to change your password.\n"
					+ "Click the link provided below and follow the instructions in order to "
					+ "access your account.\n\n" + firstAccessLink;

			System.out.println("SEND EMAIL FOR CHANGE PASSWORD");
			break;
		}

		ms.sendMail(emailSender, receiverEmail, emailSubject, emailContent);
	}

	@Override
	public boolean existentWorkemail(String workEmail) {

		List<User> userList = entityManager
				.createQuery(
						"Select u from User u "
								+ "where u.workEmail = :workEmail", User.class)
				.setParameter("workEmail", workEmail).getResultList();

		if (userList.size() != 0) {
			return true;
		} else
			return false;
	}

}
